﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace SBRepository.LinqToSql
{
    /// <summary>
    /// Linq to Sql extension methods.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Clears the cache of provided <see cref="DataContext"/>.
        /// </summary>
        /// <param name="context">The <see cref="DataContext"/> to clear cache for.</param>
        public static void ClearCache(this DataContext context)
        {
            const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            context.GetType().GetMethod("ClearCache", flags).Invoke(context, null);
        }

        /// <summary>
        /// Detaches specified entity from the underlying context.
        /// If parent/child properties are not loaded before detaching - they will become nulls.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity to detach.</typeparam>
        /// <param name="table">The table.</param>
        /// <param name="entity">The entity to detach.</param>
        /// <returns>Detached entity.</returns>
        public static TEntity Detach<TEntity>(this Table<TEntity> table, TEntity entity) where TEntity : class
        {
            return Detacher.Detach(entity);
        }

        /// <summary>
        /// Specifies what child entities to load with the main entity (LoadOptions analogue).
        /// Based on the Damien's code - http://damieng.com/
        /// </summary>
        /// <typeparam name="TEntity">The type of the query entity.</typeparam>
        /// <typeparam name="TInclude">The type of the child entity to include.</typeparam>
        /// <param name="query">Original query.</param>
        /// <param name="selector">Selector specifying what child entities to load.</param>
        /// <returns>Original query, but having enough information which child entities to load in the same query.</returns>
        public static IEnumerable<TEntity> Include<TEntity, TInclude>(this IQueryable<TEntity> query, Expression<Func<TEntity, TInclude>> selector)
        {
            var elementParameter = selector.Parameters.Single();
            var pairType = typeof(Pair<TEntity, TInclude>);
            var selectorExpression = Expression.Lambda<Func<TEntity, Pair<TEntity, TInclude>>>(
               Expression.New(pairType.GetConstructor(new[] { typeof(TEntity), typeof(TInclude) }),
                  new Expression[] { elementParameter, selector.Body },
                  pairType.GetProperty("First"), pairType.GetProperty("Second")),
               elementParameter);

            return query.Select(selectorExpression).AsEnumerable().Select(pair => pair.First);
        }

        /// <summary>
        /// Represents the pair of two elements.
        /// </summary>
        /// <typeparam name="TFirst">The type of the first element.</typeparam>
        /// <typeparam name="TSecond">The type of the second element.</typeparam>
        private class Pair<TFirst, TSecond>
        {
            /// <summary>
            /// Initializes the new instance of type <see cref="Pair"/>.
            /// </summary>
            public Pair() 
            { 
            }

            /// <summary>
            /// Initializes the new instance of type <see cref="Pair"/>.
            /// </summary>
            /// <param name="first">The first element.</param>
            /// <param name="second">The second element.</param>
            public Pair(TFirst first, TSecond second)
            {
                First = first;
                Second = second;
            }

            /// <summary>
            /// The first element.
            /// </summary>
            public TFirst First { get; set; }

            /// <summary>
            /// The second element.
            /// </summary>
            public TSecond Second { get; set; }
        }
    }
}
